package com.school.fuyuan.common.reflect;

import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.common.collect.ImmutableMap;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zrhong
 * @version 1.0
 * @date 2019/3/3 20:47
 * @description
 */
public final class MethodAccessor implements Serializable {

    private static final long serialVersionUID = -2869964174679406053L;

    //private final Class<?> clazz;
    private final String className;
    /*** 与构造时的Class对应的MethodAccess */
    private final MethodAccess methodAccess;
    /*** 构造时的Class所持有的非private方法的入参泛型Type列表 */
    private final Type[][] genericParameterTypes;
    /*** 构造时的Class所持有的非private方法的返回值泛型Type列表 */
    private final Type[] genericReturnTypes;
    /**
     * MethodAccess使用索引调用方法执行比直接通过方法名调用方法执行更快
     * key:
     * <p>
     * 1、第一个clazz.getName() + "." + method.getName()，仅供查找getter和setter的索引使用
     * 2、method.toString().subString(method.toString().indexOf(clazz.getName()))
     * <p>
     * value:
     * 以下数组的index （它们的大小都一样，一一对应一个Method）
     * {@link MethodAccess#methodNames}
     * {@link MethodAccess#parameterTypes}
     * {@link MethodAccess#returnTypes}
     * {@link MethodAccess#returnTypes}
     * {@link #genericParameterTypes}
     * {@link #genericReturnTypes}
     */
    private final Map<String, Integer> methodNameIndexMap;
    /**
     * 构造时的Class的变量名数组
     * 注意：fieldNames的大小与以上数组极有可能不同，且非一一对应
     */
    private final String[] fieldNames;

    public MethodAccessor(Class<?> clazz) {

        MethodAccess methodAccess = MethodAccess.get(clazz);

        //----------------------------------------------------
        //为了拿到methodAccess中与methodNames一一对应的Method，
        //参考MethodAccess中的逻辑再拿一遍methods，
        //毕竟此构造只调用一次做初始化，稍微牺牲一下，后面的invoke操作就一劳永逸了
        List<Method> methods = new ArrayList<>();
        boolean isInterface = clazz.isInterface();
        if (!isInterface) {
            Class<?> nextClass = clazz;
            while (nextClass != Object.class) {
                addDeclaredMethodsToList(nextClass, methods);
                nextClass = nextClass.getSuperclass();
            }
        } else {
            recursiveAddInterfaceMethodsToList(clazz, methods);
        }
        //----------------------------------------------------

        String className = clazz.getName();
        int size = methods.size();
        Type[][] genericParameterTypes = new Type[size][];
        Type[] genericReturnTypes = new Type[size];
        Map<String, Integer> methodNameIndexMap = new HashMap<>();
        List<String> fieldNames = new ArrayList<>();

        for (int i = 0; i < size; i++) {
            Method method = methods.get(i);

            genericParameterTypes[i] = method.getGenericParameterTypes();
            genericReturnTypes[i] = method.getGenericReturnType();

            String methodName = method.getName();

            //-----------------------------------------------------------------
            //特殊处理getter和setter方法的key，然后存入methodNameIndexMap中
            //-----------------------------------------------------------------
            //methodNames中没有Object.class中的final方法(如getClass)，所以这里get开头的不会有getClass
            if (methodName.length() > 3) {
                if (methodName.startsWith(Reflector.GETTER_PREFIX)
                        && method.getParameterCount() == 0) {
                    fieldNames.add(StringUtils.uncapitalize(methodName.substring(3)));
                    methodNameIndexMap.put(className + Reflector.SEPARATOR + methodName, i);
                } else if (methodName.startsWith(Reflector.SETTER_PREFIX)
                        && method.getParameterCount() == 1) {
                    //如public void com.aspire.eab.business.model.po.Contact.setContactId(java.lang.String)
                    //去掉(String contactId)存入com.aspire.eab.business.model.po.Contact.setContactId为key
                    //且如果有多个匹配com.aspire.eab.business.model.po.Contact.setContactId时只存第一个
                    methodNameIndexMap.putIfAbsent(className + Reflector.SEPARATOR + methodName, i);
                }
            }

            //-----------------------------------------------------------------
            //储存取到的所有Method的key
            //(去除方法toString后的修饰符和返回值部分) = className + methodName + "(参数列表，多个以','分隔)"
            //如HashMap的put方法的toString是"public java.lang.Object java.util.HashMap.put(java.lang.Object,java.lang.Object)"
            //那么key就是"java.util.HashMap.put(java.lang.Object,java.lang.Object)"
            //-----------------------------------------------------------------
            methodNameIndexMap.put(method.toString().replaceFirst(".+ ", ""), i);
        }

        //this.clazz = clazz;
        this.className = className;
        this.methodAccess = methodAccess;
        this.genericParameterTypes = genericParameterTypes;
        this.genericReturnTypes = genericReturnTypes;
        this.fieldNames = fieldNames.toArray(new String[0]);
        this.methodNameIndexMap = ImmutableMap.copyOf(methodNameIndexMap);//使用guava的不可变集合API
    }

    public MethodAccess getMethodAccess() {
        return methodAccess;
    }

    public Type[][] getGenericParameterTypes() {
        return genericParameterTypes;
    }

    public Type[] getGenericReturnTypes() {
        return genericReturnTypes;
    }

    public Map<String, Integer> getMethodNameIndexMap() {
        return methodNameIndexMap;
    }

    public String[] getFieldNames() {
        return fieldNames;
    }

    public Object invoke(Object obj, int methodIndex, Object... args) {
        return methodAccess.invoke(obj, methodIndex, args);
    }

    /**
     * 从对象obj的变量fieldName取值
     *
     * @param obj       变量的源对象
     * @param fieldName 变量名
     */
    public Object getFieldValue(Object obj, String fieldName) {
        Integer getterIndex = getterIndex(fieldName);
        if (getterIndex == null) {
            return null;
        }
        try {
            return invoke(obj, getterIndex);
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    String.format("通过方法(%s.%s())获取值失败",
                            obj.getClass().getName(),
                            methodAccess.getMethodNames()[getterIndex]),
                    e);
        }
    }

    /**
     * 给对象obj的变量fieldName赋值
     *
     * @param obj       变量的目标对象
     * @param fieldName 变量名
     * @param arg       变量setter方法的入参
     */
    public void setFieldValue(Object obj, String fieldName, Object arg) {
        Integer setterIndex = setterIndex(fieldName);
        if (setterIndex == null) {
            return;
        }
        try {
            invoke(obj, setterIndex, arg);
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    String.format("方法(%s.%s(%s))的入参(%s)不匹配",
                            obj.getClass().getName(),
                            methodAccess.getMethodNames()[setterIndex],
                            StringUtils.arrayToDelimitedString(methodAccess.getParameterTypes()[setterIndex], ","),
                            arg.getClass().getName()),
                    e);
        }
    }

    /**
     * 获取对象obj的getter方法的索引
     * 返回null表示fieldName无匹配的getter方法
     *
     * @param fieldName getter方法对应的变量名（这里视所有getter方法都对应一个变量）
     * @return getter方法的索引
     */
    public Integer getterIndex(String fieldName) {
        return getPojoMethodIndex(fieldName, true);
    }

    /**
     * 获取对象obj的setter方法的索引
     * 返回null表示fieldName无匹配的setter方法
     *
     * @param fieldName setter方法对应的变量名（这里视所有setter方法都对应一个变量）
     * @return setter方法的索引
     */
    public Integer setterIndex(String fieldName) {
        return getPojoMethodIndex(fieldName, false);
    }

    /*** 获取对象obj的getter或setter方法的索引 */
    private Integer getPojoMethodIndex(String fieldName, boolean isGetter) {
        if (!StringUtils.hasText(fieldName)) {
            return null;
        }
        String prefix = className + Reflector.SEPARATOR
                + (isGetter ? Reflector.GETTER_PREFIX : Reflector.SETTER_PREFIX);
        Integer index = methodNameIndexMap.get(prefix + StringUtils.capitalize(fieldName));
        //首字母小写，第二字母大写的成员变量（奇行种）
        if (index == null && Reflector.isAlienName(fieldName)) {
            return methodNameIndexMap.get(prefix + fieldName);
        }
        return index;
    }

    /**
     * 通过method.toString字符串去掉类名前面一段得到的部分获取方法索引
     * 如public void com.aspire.eab.business.model.po.Contact.setContactId(java.lang.String)
     * 去掉 "public void " 得到com.aspire.eab.business.model.po.Contact.setContactId(java.lang.String)
     *
     * @param methodStr method.toString().subString(method.toString().indexOf(clazz.getName()))对应的字符串
     * @return 方法对应的索引，null表示无匹配的方法
     */
    public Integer getMethodIndex(String methodStr) {
        return methodNameIndexMap.get(methodStr);
    }

    /**
     * 通过方法名和入参列表获取方法对应的索引
     *
     * @return 方法对应的索引，null表示无匹配的方法
     */
    public Integer getMethodIndex(String methodName, Object... args) {
        String[] methodNames = methodAccess.getMethodNames();
        Class[][] parameterTypes = methodAccess.getParameterTypes();
        c:
        for (int i = 0; i < methodNames.length; i++) {
            if (methodNames[i].equals(methodName)) {
                Class[] paramTypes = parameterTypes[i];
                if (paramTypes.length != args.length) {
                    continue;
                }
                for (int j = 0; j < paramTypes.length; j++) {
                    if (!Reflector.isInstance(paramTypes[j], args[j])) {
                        continue c;//不匹配，则跳到下一个外层循环
                    }
                }
                return i;
            }
        }
        return null;
    }

    /**
     * 添加所有clazz中声明的非private方法到methods
     * 此方法照搬{@link MethodAccess#addDeclaredMethodsToList(java.lang.Class, java.util.ArrayList)}
     */
    private void addDeclaredMethodsToList(Class<?> clazz, List<Method> methods) {
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (int i = 0; i < declaredMethods.length; i++) {
            Method method = declaredMethods[i];
            int modifiers = method.getModifiers();
            // if (Modifier.isStatic(modifiers)) continue;
            if (Modifier.isPrivate(modifiers)) {
                continue;
            }
            methods.add(method);
        }
    }

    /**
     * 添加所有interfaceClass中声明的非private方法到methods
     * 此方法照搬{@link MethodAccess#recursiveAddInterfaceMethodsToList(java.lang.Class, java.util.ArrayList)}
     */
    private void recursiveAddInterfaceMethodsToList(Class<?> interfaceClass, List<Method> methods) {
        addDeclaredMethodsToList(interfaceClass, methods);
        Class<?>[] interfaces = interfaceClass.getInterfaces();
        for (int i = 0; i < interfaces.length; i++) {
            Class<?> nextInterface = interfaces[i];
            recursiveAddInterfaceMethodsToList(nextInterface, methods);
        }
    }

}
